package com.google.code.jtasks.query;

import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import com.google.code.jtasks.connection.TaskRepoConnectionSettings;
import com.google.code.jtasks.model.IMappedTask;
import com.google.code.jtasks.model.TaskPropertyValue;
import com.google.code.jtasks.model.IMappedTask.TaskField;

/**
 * Models a query against a task system.
 */
public final class TaskQuery
{
	private final Map<TaskField, Set<TaskPropertyValue<?>>> fieldInclusiveFilters;
	private final TaskRepoConnectionSettings connectionSettings;

	/**
	 * Constructs a new {@link TaskQuery} instance.
	 * 
	 * @param connectionSettings
	 *            the {@link TaskRepoConnectionSettings} for the task repository to run the query against
	 */
	public TaskQuery(TaskRepoConnectionSettings connectionSettings)
	{
		this.connectionSettings = connectionSettings;
		this.fieldInclusiveFilters = new HashMap<TaskField, Set<TaskPropertyValue<?>>>();
	}

	/**
	 * Returns the {@link TaskRepoConnectionSettings} for the task repository to run the query against.
	 * 
	 * @return the {@link TaskRepoConnectionSettings} for the task repository to run the query against
	 */
	public TaskRepoConnectionSettings getConnectionSettings()
	{
		return connectionSettings;
	}

	/**
	 * Returns the {@link Map} of inclusive field filters represented by this {@link TaskQuery}. Each mapped
	 * {@link TaskField} specifies a {@link Set} of {@link TaskPropertyValue}s. Only tasks that match one of
	 * these {@link TaskPropertyValue}s for the specified {@link TaskField} will be included in the results.
	 * Each task must "pass" each of the {@link TaskField} filters to be included in the result.
	 * 
	 * @return the {@link Map} of inclusive field filters represented by this {@link TaskQuery}
	 */
	public Map<TaskField, Set<TaskPropertyValue<?>>> getFieldInclusiveFilters()
	{
		return Collections.unmodifiableMap(fieldInclusiveFilters);
	}

	/**
	 * Sets an inclusive for the specified field, such that only tasks whose value for the field matches one
	 * of the specified values will be included.
	 * 
	 * @param field
	 *            the {@link TaskField} to set a filter for
	 * @param values
	 *            the {@link IMappedTask} values for the field that tasks will be matched against
	 * @return this {@link TaskQuery} instance, for method-chaining purposes
	 */
	public TaskQuery filterBy(TaskField field, Set<TaskPropertyValue<IMappedTask>> values)
	{
		if (fieldInclusiveFilters.containsKey(field))
			throw new IllegalArgumentException("A filter for the specified field has already been provided: "
					+ field);

		fieldInclusiveFilters.put(field, new HashSet<TaskPropertyValue<?>>(values));

		return this;
	}

	/**
	 * Sets an inclusive for the specified field, such that only tasks whose value for the field matches one
	 * of the specified values will be included.
	 * 
	 * @param field
	 *            the {@link TaskField} to set a filter for
	 * @param values
	 *            the values for the field that tasks will be matched against
	 * @return this {@link TaskQuery} instance, for method-chaining purposes
	 */
	public TaskQuery filterBy(TaskField field, TaskPropertyValue<IMappedTask>... values)
	{
		Set<TaskPropertyValue<IMappedTask>> valuesSet = new HashSet<TaskPropertyValue<IMappedTask>>();
		for (TaskPropertyValue<IMappedTask> value : values)
			valuesSet.add(value);

		return filterBy(field, valuesSet);
	}

	/**
	 * Sets an inclusive for the specified field, such that only tasks whose value for the field matches one
	 * of the specified values will be included.
	 * 
	 * @param field
	 *            the {@link TaskField} to set a filter for
	 * @param values
	 *            the values for the field that tasks will be matched against
	 * @return this {@link TaskQuery} instance, for method-chaining purposes
	 */
	public TaskQuery filterBy(TaskField field, Object... values)
	{
		Set<TaskPropertyValue<IMappedTask>> valuesSet = new HashSet<TaskPropertyValue<IMappedTask>>();
		for (Object value : values)
			valuesSet.add(new TaskPropertyValue<IMappedTask>(field, value));

		return filterBy(field, valuesSet);
	}
}
